
#include <BMap>
#include <BRender>
#include <BOpenGL>

#include <BSplineCurve>
#include "member_BSplineCurve.h"

using namespace BWE;

#define member					(*(member_BSplineCurve*)_ptr)
#define member_allocate()		_ptr = new member_BSplineCurve(this)
#define member_release()		delete (member_BSplineCurve*)_ptr

BSplineCurve::BSplineCurve()
{
	member_allocate();
}
BSplineCurve::~BSplineCurve()
{
	member_release();
}

void BSplineCurve::copy(const BShape* other)
{
	BShape::copy(other);
	if (const BSplineCurve* spline = dynamic_cast<const BSplineCurve*>(other))
	{
		member.thickness = spline_member(spline)->thickness;
		member.thickness = spline_member(spline)->closeLoop;
		member.segments = spline_member(spline)->segments;
		member.length = spline_member(spline)->length;
		member.dirty = spline_member(spline)->dirty;
	}
}
BShape* BSplineCurve::clone() const
{
	BSplineCurve* stripe = new BSplineCurve();
	stripe->copy(this);
	return stripe;
}

void BSplineCurve::setVertices(BVectorArray* vertices)
{
	member.vertices = vertices;
	this->dirty();
}
const BVectorArray* BSplineCurve::vertices() const
{
	return member.vertices;
}
BVectorArray* BSplineCurve::vertices()
{
	return member.vertices;
}

void BSplineCurve::setCloseLoop(bool closeLoop)
{
	if (member.closeLoop != closeLoop)
	{
		member.closeLoop = closeLoop;
		this->dirty();
	}
}
bool BSplineCurve::closeLoop() const
{
	return member.closeLoop;
}

void BSplineCurve::setThickness(BReal thickness)
{
	if (member.thickness != thickness)
	{
		member.thickness = thickness;
		this->dirty();
	}
}
BReal BSplineCurve::thickness() const
{
	return member.thickness;
}

bool BSplineCurve::reverse()
{
	if (member.vertices->size())
	{
		member.vertices->reverse();
		this->dirty();
		return true;
	}
	return false;
}

BReal BSplineCurve::length() const
{
	this->flush();
	return member.length;
}
BVector BSplineCurve::point(BReal s) const
{
	if (member.segments.empty())
		return BVector();
	s = bClamp(s, 0.0, member.length);


	Segment* closestSegment = 0;
	for (int i = 0; i < member.segments.size(); i++)
	{
		Segment& segment = member.segments[i];
		if (s >= segment.begin && s < segment.begin + segment.length)
		{
			closestSegment = &segment;
			BReal left = 0;
			BReal right = 1;
			s -= segment.begin;
			BReal remaining = s - segment.length;
			for (int iter = 0; iter < 100; ++iter)
			{
				const BReal mid = (left + right) / 2;
				const BReal integral = segment.adaptiveSimpson(0, mid, 1e-6);
				if (std::abs(integral - remaining) < 1e-6)
				{
					return segment.vertex(mid);
				}
				(integral < remaining) ? left = mid : right = mid;
			}
			return segment.vertex((left + right) / 2);
		}
	}
	return member.vertices->first();
}
BReal BSplineCurve::closest(const BVector& vertex, BVector& result, BVector& direct) const
{
	if (member.segments.empty())
		return 0;

	Segment* minSegment = 0;
	BReal minDist = REAL_MAX;
	int minIndex = 0;
	BReal bestT = 0;

	for (int i = 0; i < member.segments.size(); ++i)
	{
		Segment& segment = member.segments[i];
		BReal s = BReal(1) / segment.vertices.size();
		for (int j = 0; j <= segment.vertices.size(); ++j)
		{
			const BVector& v = segment.vertices(j);
			const BReal dist = (v - vertex).length2();
			if (dist < minDist)
			{
				bestT = j * s;
				minIndex = i;
				minDist = dist;
				minSegment = &segment;
			}
		}
	}

	if (minIndex == 0 && bestT == 0 && member.closeLoop)
	{
		BVector v = minSegment->vertex(bestT);
		BVector dir = minSegment->direct(bestT);
		if ((vertex - v).dot(dir) < 0)
		{
			minSegment = &member.segments.last();
			BReal s = BReal(1) / minSegment->vertices.size();
			bestT = s * (minSegment->vertices.size() - 1);
		}
	}

	if (!minSegment)
		return 0;

	for (int iter = 0; iter < 20; ++iter)
	{
		const BVector current = minSegment->vertex(bestT);
		const BVector delta = current - vertex;
		const BVector deriv1 = minSegment->direct(bestT);
		const BVector deriv2 = minSegment->secondDirect(bestT);

		const BReal prime = 2 * delta.dot(deriv1);
		const BReal doublePrime = 2 * (deriv1.dot(deriv1) + delta.dot(deriv2));
		if (std::abs(prime) < 1e-6 || doublePrime == 0)
			break;

		bestT -= prime / doublePrime;
	}

	result = minSegment->vertex(bestT);
	direct = minSegment->direct(bestT).normal();
	return minSegment->begin + minSegment->length * bestT + direct.dot(vertex - result);
}

bool BSplineCurve::query(const BString& text, BValue& value) const
{
	if (text == "vertices")
	{
		value = member.vertices;
		return true;
	}
	if (text == "all-vertices")
	{
		BVectorArray& vertices = value.fill<BVectorArray>();
		vertices.reset();
		for (int i = 0; i < member.segments.size(); i++)
		{
			Segment& segment = member.segments[i];
			if (segment.vertices.size())
			{
				vertices.append(segment.vertices);
			}
			else
			{
				vertices.append(member.vertices->at(i));
			}
		}
		return true;
	}
	return BShape::query(text, value);
}

BVector BSplineCurve::vertex(const BVector& direct) const
{
	return BVector();
}
BVector BSplineCurve::inertia(BReal mass) const
{
	return BVector();
}

void BSplineCurve::refresh(BBox& box)
{
	BSpace space = member.vertices->first();
	for (int i = 0; i < member.vertices->size(); i++)
	{
		space.expand(member.vertices->at(i));
	}

	member.length = 0;
	if (member.vertices->size() > 1)
	{
		const int n = member.vertices->size();
		BArray<BReal> x(n), y(n), z(n);
		for (int i = 0; i < n; ++i)
		{
			x[i] = member.vertices->at(i).x();
			y[i] = member.vertices->at(i).y();
			z[i] = member.vertices->at(i).z();
		}

		BArray<BReal> Mx, My, Mz;
		if (member.closeLoop)
		{
			x.insert(0, member.vertices->last().x());
			y.insert(0, member.vertices->last().y());
			z.insert(0, member.vertices->last().z());

			x.append(member.vertices->first().x());
			y.append(member.vertices->first().y());
			z.append(member.vertices->first().z());

			Mx = member.computeCoefficients(x);
			My = member.computeCoefficients(y);
			Mz = member.computeCoefficients(z);

			member.segments.resize(n);
			for (int i = 0; i < n; ++i)
			{
				int c = i + 1;
				int n = i + 2;
				member.segments[i] = {
					x[c], (x[n] - x[c]) - (2 * Mx[c] + Mx[n]) / 6, Mx[c] / 2, (Mx[n] - Mx[c]) / 6,
					y[c], (y[n] - y[c]) - (2 * My[c] + My[n]) / 6, My[c] / 2, (My[n] - My[c]) / 6,
					z[c], (z[n] - z[c]) - (2 * Mz[c] + Mz[n]) / 6, Mz[c] / 2, (Mz[n] - Mz[c]) / 6
				};
			}
		}
		else
		{
			Mx = member.computeCoefficients(x);
			My = member.computeCoefficients(y);
			Mz = member.computeCoefficients(z);

			member.segments.resize(n - 1);
			for (int i = 0; i < n - 1; ++i)
			{
				int c = i;
				int n = i + 1;
				member.segments[i] = {
					x[c], (x[n] - x[c]) - (2 * Mx[c] + Mx[n]) / 6, Mx[c] / 2, (Mx[n] - Mx[c]) / 6,
					y[c], (y[n] - y[c]) - (2 * My[c] + My[n]) / 6, My[c] / 2, (My[n] - My[c]) / 6,
					z[c], (z[n] - z[c]) - (2 * Mz[c] + Mz[n]) / 6, Mz[c] / 2, (Mz[n] - Mz[c]) / 6
				};
			}
		}
		for (int i = 0; i < member.segments.size(); ++i)
		{
			Segment& segment = member.segments[i];
			segment.freshPoints(100);
			segment.begin = member.length;
			segment.length = segment.adaptiveSimpson(0, 1, 1e-6);
			member.length += segment.length;
		}
	}

	box = space;
	box.lena() += member.thickness;
	box.lenb() += member.thickness;
	box.lenc() += member.thickness;
}
void BSplineCurve::render(BRender& render)
{
	if (member.segments.size() > 1)
	{
		if (member.closeLoop)
		{
			glBegin(GL_LINE_LOOP);
			for (int i = 0; i < member.segments.size(); i++)
			{
				Segment& segment = member.segments[i];
				for (int j = 0; j < segment.vertices.size(); j++)
				{
					const BVector& point = segment.vertices[j];
					BOpenGL::glVertex(point.x(), point.y(), point.z());
				}
			}
			glEnd();
		}
		else
		{
			glBegin(GL_LINE_STRIP);
			for (int i = 0; i < member.segments.size(); i++)
			{
				Segment& segment = member.segments[i];
				for (int j = 0; j < segment.vertices.size(); j++)
				{
					const BVector& point = segment.vertices[j];
					BOpenGL::glVertex(point.x(), point.y(), point.z());
				}
			}
			const BVector& last = member.vertices->last();
			BOpenGL::glVertex(last.x(), last.y(), last.z());
			glEnd();
		}
	}

	if (member.segments.size() > 1)
	{
		render.setColor(200, 0, 0);
		render.setPointSize(3);
		render.setPointSmooth(true);
		glBegin(GL_POINTS);
		for (int i = 0; i < member.segments.size(); i++)
		{
			Segment& segment = member.segments[i];
			for (int j = 0; j < segment.vertices.size(); j++)
			{
				const BVector& point = segment.vertices[j];
				BOpenGL::glVertex(point.x(), point.y(), point.z());
			}
		}
		glEnd();
	}

	if (member.vertices->size())
	{
		render.setColor(200, 200, 200);
		render.setPointSize(6);
		glBegin(GL_POINTS);
		for (int i = 0; i < member.vertices->size(); i++)
		{
			const BVector& point = member.vertices->at(i);
			BOpenGL::glVertex(point.x(), point.y(), point.z());
		}
		glEnd();
	}

}
